﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Diagrams.View;
using Microsoft.VisualStudio.GraphModel;
using Microsoft.VisualStudio.Progression;
using Microsoft.VisualStudio.GraphModel.Styles;

namespace Timtopia.DGMLPosterView
{
    public static class RandomModularGraphGenerator
    {
        static Random rand;
        public static Graph Generate()
        {
            Graph graph;
            rand = new Random();
            using (var scope = new GraphTransactionScope())
            {
                graph = new Graph();
                graph.Styles.Add(CreateStyle(graph, typeof(GraphLink), "HasCategory('ShortestPath')", "Stroke", "Orange", "ShortestPath", "ShortestPath"));
                graph.Styles.Add(CreateStyle(graph, typeof(GraphNode), "HasCategory('StartNode')", "Label", "Start", "StartNode", "StartNode"));
                graph.Styles.Add(CreateStyle(graph, typeof(GraphNode), "HasCategory('EndNode')", "Label", "End", "EndNode", "EndNode"));
                graph.Styles.Add(CreateStyle(graph, typeof(GraphNode), "HasCategory('EndNode')", "Label", "End", "EndNode", "EndNode"));
                graph.AddSchema(DgmlCommonSchema.Schema);
                graph.AddSchema(ModularDecomposition.Schema);
                graph.AddSchema(PowerGraphDecomposition.Schema);
                graph.SetValue(GraphObjectExtensions.Layout, "toptobottom");
                PopulateGraph(graph, 15);

                scope.Complete();
            }
            return graph;
        }

        private static GraphConditionalStyle CreateStyle(Graph graph, Type targetType, string expression, string property, string value, string groupLabel, string valueLabel)
        {
            var s = new GraphConditionalStyle(graph);
            var gc = new GraphCondition(s);
            gc.Expression = expression;
            var setter = new GraphSetter(s, property);
            setter.Value = value;
            s.Setters.Add(setter);
            s.Conditions.Add(gc);
            s.TargetType = targetType;
            s.GroupLabel = groupLabel;
            s.ValueLabel = valueLabel;
            return s;
        }

        internal static void PopulateGraph(Graph graph, int n)
        {
            // Generate N nodes with labels a..alpha[n]
            List<int> ids = new List<int>(Enumerable.Range(0, n));

            RecursiveModularize(graph, ids);

            //PopulateTestGraph(graph);
        }

        private static void RecursiveModularize(Graph graph, List<int> ids)
        {
            List<List<int>> modules = new List<List<int>>();
            Modularize(ids, modules);
            LinkModules(graph, ids.Count, modules);
            foreach (var m in modules)
            {
                if (m.Count > 2 && rand.Next(2) == 1)
                {
                    RecursiveModularize(graph, m);
                }
            }
        }

        private static void LinkModules(Graph graph, int n, List<List<int>> modules, double forwardProbability = 0.25, double reverseProbability = 0.25)
        {
            int outerModule = 0, innerModule = 0;
            foreach (var m0 in modules)
            {
                // uncomment to see groups
                //var g = graph.Nodes.GetOrCreate("g" + gid++);
                //g.IsGroup = true;
                //foreach (var i in m)
                //{
                //    var v = graph.Nodes.GetOrCreate(ModularDecomposition.GetID(n, i));
                //    var clink = graph.Links.GetOrCreate(g, v);
                //    clink.AddCategory(GraphCommonSchema.Contains);
                //}
                innerModule = 0;
                foreach (var m1 in modules)
                {
                    double r = rand.NextDouble();
                    if (innerModule > outerModule && r < forwardProbability || innerModule < outerModule && r < reverseProbability)
                    {
                        foreach (var i in m0)
                        {
                            foreach (var j in m1)
                            {
                                if (i == j) continue;
                                var u = graph.Nodes.GetOrCreate(ModularDecomposition.GetID(n, i));
                                var v = graph.Nodes.GetOrCreate(ModularDecomposition.GetID(n, j));
                                graph.Links.GetOrCreate(u, v);
                            }
                        }
                    }
                    innerModule++;
                }
                outerModule++;
            }
        }

        private static void LinkNodeToModule(Graph g, GraphNode v, List<int> module, bool reverse = false)
        {
            int n = g.Nodes.Count;
            foreach (var i in module)
            {
                var u = g.Nodes.GetOrCreate(ModularDecomposition.GetID(n, i));
                if (reverse)
                {
                    g.Links.GetOrCreate(u, v);
                }
                else
                {
                    g.Links.GetOrCreate(v, u);
                }
            }
        }

        /// <summary>
        /// randomly choose selections of nodes from ids to be a modules
        /// </summary>
        /// <param name="ids"></param>
        /// <param name="modules"></param>
        private static void Modularize(List<int> ids, List<List<int>> modules)
        {
            while (ids.Count > 0)
            {
                List<int> module = new List<int>();
                int mSize = ids.Count == 1 ? 1 : 0;
                while (mSize == 0)
                {
                    mSize = rand.Next(ids.Count);
                }
                for (int i = 0; i < mSize; ++i)
                {
                    int j = rand.Next(ids.Count);
                    module.Add(ids[j]);
                    ids.RemoveAt(j);
                }
                modules.Add(module);
            }
        }

        /// <summary>
        /// just a useful test graph with a really tricky path to follow from the node marked "Start" to the "End" node.
        /// </summary>
        /// <param name="g"></param>
        private static void PopulateTestGraph(Graph g)
        {
            int n = 13;
            for (int i = 0; i < n; i++)
            {
                g.Nodes.GetOrCreate(ModularDecomposition.GetID(n, i));
            }
            var m0 = new List<int>();
            m0.Add(GetNodeIndex(g, "A"));
            m0.Add(GetNodeIndex(g, "E"));
            m0.Add(GetNodeIndex(g, "G"));
            m0.Add(GetNodeIndex(g, "H"));
            m0.Add(GetNodeIndex(g, "K"));
            m0.Add(GetNodeIndex(g, "L"));

            var m1 = new List<int>();
            m1.Add(GetNodeIndex(g, "B"));
            m1.Add(GetNodeIndex(g, "C"));
            m1.Add(GetNodeIndex(g, "D"));
            m1.Add(GetNodeIndex(g, "B"));
            m1.Add(GetNodeIndex(g, "F"));
            m1.Add(GetNodeIndex(g, "I"));
            m1.Add(GetNodeIndex(g, "J"));
            m1.Add(GetNodeIndex(g, "M"));

            var m2 = new List<int>();
            m2.Add(GetNodeIndex(g, "C"));
            m2.Add(GetNodeIndex(g, "J"));

            var m3 = new List<int>();
            m3.Add(GetNodeIndex(g, "I"));
            m3.Add(GetNodeIndex(g, "F"));
            m3.Add(GetNodeIndex(g, "B"));
            m3.Add(GetNodeIndex(g, "M"));
            //var modules = new List<List<int>>();
            //modules.Add(m0);
            //modules.Add(m1);
            //modules.Add(m2);
            //modules.Add(m3);
            //CreateModules(g, modules);
            LinkNodeToModule(g, g.Nodes.GetOrCreate("D"), m2);
            LinkNodeToModule(g, g.Nodes.GetOrCreate("D"), m3, reverse: true);
            LinkModules(g, n, new List<List<int>>(new[] { m3, m2 }), 1, 0);
            LinkModules(g, n, new List<List<int>>(new[] { m0, m1 }), 1, 0);
            g.Links.GetOrCreate(g.Nodes.Get("I"), g.Nodes.Get("L"));
            g.Links.GetOrCreate(g.Nodes.Get("I"), g.Nodes.Get("H"));
            g.Links.GetOrCreate(g.Nodes.Get("D"), g.Nodes.Get("G"));
            g.Links.GetOrCreate(g.Nodes.Get("D"), g.Nodes.Get("E"));
            g.Nodes.GetOrCreate("L").Label = "End";
            g.Nodes.GetOrCreate("M").Label = "Start";
        }
        private static void CreateModules(Graph g, List<List<int>> modules)
        {
            var l = new List<GraphNode>(g.Nodes);
            int i = 0;
            foreach(var m in modules) {
                var group = g.Nodes.GetOrCreate("module" + (i++));
                group.IsGroup = true;
                foreach (var j in m)
                {
                    var v = l[j];
                    var link = g.Links.GetOrCreate(group, v);
                    link.AddCategory(GraphCommonSchema.Contains);
                }
            }
        }
        private static int GetNodeIndex(Graph g, string id) 
        {
            int i = 0;
            foreach (var v in g.Nodes)
            {
                if (v.Id.ToString() == id)
                {
                    return i;
                }
                i++;
            }
            return -1;
        }
    }
}
